package com.hzncc.flowable_diboot.custom_field;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.diboot.core.entity.BaseModel;
import com.diboot.core.service.BaseService;
import com.diboot.core.util.ContextHolder;
import com.diboot.core.util.JSON;
import com.diboot.core.util.S;
import com.hzncc.flowable_diboot.custom_field.entity.CustomField;
import com.hzncc.flowable_diboot.custom_field.entity.CustomFieldValue;
import com.hzncc.flowable_diboot.custom_field.exception.CustomFieldException;
import com.hzncc.flowable_diboot.custom_field.mapper.CustomFieldValueMapper;
import com.hzncc.flowable_diboot.custom_field.service.CustomFieldService;
import com.hzncc.flowable_diboot.custom_field.service.CustomFieldValueService;
import com.hzncc.flowable_diboot.custom_field.vo.CustomFieldValueListVO;
import lombok.extern.log4j.Log4j;

import java.util.*;
import java.util.stream.Collectors;

@Log4j
public class CustomFieldUtil {

    private static CustomFieldService customFieldService;

    public static CustomFieldService getCustomFieldService() {
        if (customFieldService == null) {
            customFieldService = ContextHolder.getBean(CustomFieldService.class);
        }
        return customFieldService;
    }

    public static CustomFieldValueService customFieldValueService;

    public static CustomFieldValueService getCustomFieldValueService() {
        if (customFieldValueService == null) {
            customFieldValueService = ContextHolder.getBean(CustomFieldValueService.class);
        }
        return customFieldValueService;
    }

    private static final Class<?>[] primitiveTypes = {
            Boolean.class, Character.class, Byte.class,
            Short.class, Integer.class, Long.class,
            Float.class, Double.class, Void.class
    };

    private static final Set<String> primitiveTypeNames = Arrays.stream(primitiveTypes).map(Class::getSimpleName).collect(Collectors.toSet());

    /**
     * 获取自定义字段值
     *
     * @param entityId 实体ID
     * @return
     */
    public static Map<String, Object> getCustomFieldValue(String entityId) {
        CustomFieldValueMapper mapper = ContextHolder.getBean(CustomFieldValueMapper.class);
        List<Map<String, Object>> customFieldValue = mapper.getCustomFieldValue(entityId);
        if (customFieldValue == null) {
            return null;
        }
        Map<String, Object> fieldValue = new HashMap<>();
        for (Map<String, Object> map : customFieldValue) {
            Object fieldName = map.get("fieldName");
            fieldValue.put(fieldName.toString(), fieldValueToObject(StrUtil.toStringOrNull(map.get("value")), StrUtil.toStringOrNull(map.get("dateType"))));
        }
        return fieldValue;
    }


    public static <VO extends BaseModel> void bindCustomFieldValue(VO vo, String type) {
        bindCustomFieldValue(Collections.singletonList(vo), type);
    }

    /**
     * 根据自定义字段类型绑定自定义字段值到列表
     *
     * @param voList 泛型列表，包含待绑定自定义字段值的实体对象
     * @param type   自定义字段的类型
     */
    public static <VO extends BaseModel> void bindCustomFieldValue(List<VO> voList, String type) {
        // 获取自定义字段服务实例
        CustomFieldService customFieldService = getCustomFieldService();
        // 根据类型获取自定义字段列表
        List<CustomField> customFieldListByType = customFieldService.getCustomFieldListByType(type);
        // 如果自定义字段列表为空，则直接返回
        if (customFieldListByType == null || customFieldListByType.isEmpty()) {
            return;
        }
        // 提取实体列表的ID
        List<String> ids = voList.stream().map(BaseModel::getId).collect(Collectors.toList());
        // 获取自定义字段值服务实例
        CustomFieldValueService customFieldValueService = getCustomFieldValueService();
        // 根据实体ID列表获取自定义字段值列表
        List<CustomFieldValueListVO> customFieldValues = customFieldValueService.getCustomFieldValue(ids);
        // 将自定义字段值列表按实体ID分组
        Map<String, List<CustomFieldValueListVO>> customFieldValueMap = customFieldValues.stream().collect(Collectors.groupingBy(CustomFieldValue::getEntityId));
        // 将实体列表转换为ID到实体对象的映射
        Map<String, VO> voMap = voList.stream().collect(Collectors.toMap(BaseModel::getId, v -> v));
        // 遍历分组后的自定义字段值，将其绑定到对应的实体对象上
        for (Map.Entry<String, List<CustomFieldValueListVO>> entry : customFieldValueMap.entrySet()) {
            VO vo = voMap.get(entry.getKey());
            // 将自定义字段值列表转换为自定义字段Map，并设置到实体对象中
            vo.setExtField(toMap(entry.getValue(), customFieldListByType));
        }
    }

    /**
     * 将自定义字段列表转换为映射
     *
     * @param value        自定义字段值列表
     * @param customFields 自定义字段列表
     * @return 包含字段名和对应值的映射
     */
    private static Map<String, Object> toMap(List<CustomFieldValueListVO> value, List<CustomField> customFields) {
        // 将自定义字段值列表转换为以字段ID为键的映射
        Map<String, CustomFieldValueListVO> valueMap = value.stream().collect(Collectors.toMap(CustomFieldValue::getFieldId, e -> e));
        // 初始化最终的字段名和值的映射
        Map<String, Object> customFieldValue = new HashMap<>();
        // 遍历自定义字段列表
        for (CustomField customField : customFields) {
            // 通过字段ID从valueMap中获取对应的字段值对象
            CustomFieldValueListVO customFieldValueListVO = valueMap.get(customField.getId());
            // 如果字段值对象为空，则跳过当前字段
            if (customFieldValueListVO == null) {
                continue;
            }
            // 将字段名和转换后的值添加到最终的映射中
            customFieldValue.put(customField.getFieldName(), fieldValueToObject(customFieldValueListVO.getValue(), customField.getDataType()));
        }
        // 返回包含字段名和对应值的映射
        return customFieldValue;
    }

    public static <VO extends BaseModel> void saveCustomFieldValue(VO vo, String type) {
        saveCustomFieldValue(Collections.singletonList(vo), type);
    }

    /**
     * 保存自定义字段值
     * 该方法用于保存一组实体对象的自定义字段值它首先检查实体对象列表是否为空，
     * 然后获取自定义字段服务和实体ID列表，接着从数据库中获取这些实体的自定义字段值，
     * 并将其映射为一个易于访问的map之后，它遍历每个实体对象和它们的自定义字段，
     * 创建新的自定义字段值对象，填充必要的信息，并将这些对象添加到一个列表中最后，
     * 使用自定义字段值服务和基础服务来创建或更新这些自定义字段值和实体对象
     *
     * @param voList 实体对象列表，不能为空
     * @param type   实体对象的类型，用于获取相应的自定义字段
     */
    public static <VO extends BaseModel> void saveCustomFieldValue(List<VO> voList, String type) {
        // 检查voList是否为空，如果为空则不执行保存操作
        if (voList == null || voList.isEmpty()) {
            log.warn("voList为空，不执行保存自定义字段值操作");
            return;
        }
        // 获取自定义字段服务实例
        CustomFieldService customFieldService = getCustomFieldService();
        // 通过流操作获取实体ID列表
        List<String> entityIds = voList.stream().map(BaseModel::getId).collect(Collectors.toList());
        // 获取指定实体ID列表的自定义字段值列表
        List<CustomFieldValueListVO> customFieldValueList = customFieldValueService.getCustomFieldValue(entityIds);
        // 将自定义字段值列表映射为一个以字段ID和实体ID组合为键的map
        Map<String, String> existCustomerValueMap = customFieldValueList.stream().collect(Collectors.toMap(e -> e.getFieldId() + e.getEntityId(), e -> e.getId()));
        // 创建一个用于存储新的自定义字段值的列表
        List<CustomFieldValue> customFieldValues = new ArrayList<>();
        // 根据类型获取自定义字段的map
        Map<String, CustomField> customFieldMapByType = customFieldService.getCustomFieldMapByType(type);
        // 遍历每个实体对象，为其自定义字段创建新的自定义字段值对象
        for (VO vo : voList) {
            Map<String, Object> customField = vo.getExtField();
            if (customField == null) {
                continue;
            }
            for (Map.Entry<String, Object> entry : customField.entrySet()) {
                CustomFieldValue value = new CustomFieldValue();
                CustomField field = customFieldMapByType.get(entry.getKey());
                // 如果自定义字段不存在，则抛出异常
                if (field == null) {
                    log.error("业务" + type + "字段" + entry.getKey() + "不存在");
                    throw new CustomFieldException("业务" + type + "字段" + entry.getKey() + "不存在");
                }
                // 设置自定义字段值对象的值、实体ID、字段ID和ID
                String val = null;
                try {
                    val = fieldValueToString(entry.getValue(), field.getDataType());
                } catch (Exception e) {
                    log.error("字段" + entry.getKey() + "类型转换异常", e);
                    throw new CustomFieldException("字段" + entry.getKey() + "类型转换异常");
                }
                value.setValue(val);
                value.setEntityId(vo.getId());
                value.setFieldId(field.getId());
                value.setId(existCustomerValueMap.get(field.getId() + vo.getId()));
                // 将新的自定义字段值对象添加到列表中
                customFieldValues.add(value);
            }
        }

        // 使用自定义字段值服务创建或更新这些自定义字段值
        customFieldValueService.createOrUpdateEntities(customFieldValues);
        // 获取基础服务实例，用于操作实体对象
        BaseService baseService = ContextHolder.getBaseServiceByEntity(voList.get(0).getClass());
        // 使用基础服务创建或更新实体对象列表
        baseService.createOrUpdateEntities(voList);
    }

    // 判断给定对象是否为基本类型或基本类型的包装类
    private static boolean isPrimitiveType(Object obj) {
        // 如果对象为null，则不认为是基本类型
        if (obj == null) {
            return false;
        }

        // 获取对象的类类型
        Class<?> clazz = obj.getClass();

        // 判断是否是基本类型的包装类
        for (Class<?> primitiveType : primitiveTypes) {
            if (primitiveType.isInstance(obj)) {
                return true;
            }
        }

        // 判断是否是基本类型本身
        if (clazz.isPrimitive()) {
            return true;
        }

        // 判断是否是 String 类型，也认为是基本类型
        return obj instanceof String;
    }

    /**
     * 将字段值转换为字符串
     * 如果值为null，则返回null；如果数据类型为原始类型，则直接转换为字符串；
     * 否则，使用JSON.stringify方法进行序列化
     *
     * @param value    字段的值，可以是任何类型
     * @param dataType 字段的数据类型名称，用于确定转换方式
     * @return 转换后的字符串表示
     */
    private static String fieldValueToString(Object value, String dataType) {
        // 检查值是否为null，如果是，则直接返回null
        if (value == null) {
            return null;
        }
        // 检查数据类型是否为原始类型，如果是，则直接转换为字符串
        if (primitiveTypeNames.contains(dataType)) {
            return String.valueOf(value);
        }
        if (S.equals(dataType, "String")) {
            return (String) value;
        }
        // 对于非原始类型，使用JSON.stringify方法进行序列化
        return JSON.stringify(value);
    }

    /**
     * 将字段值转换为对应数据类型的对象
     *
     * @param value    字段的值字符串表示
     * @param dataType 字段的数据类型名称
     * @return 对应数据类型的对象如果输入值为null，则返回null
     * <p>
     * 此方法旨在将数据库中以字符串形式存储的字段值转换为其对应的数据类型对象
     * 它处理了原始类型直接返回字符串值的情况，以及通过JSON解析复杂类型的情况
     * 当数据类型为自定义类型且无法通过JSON直接转换时，将抛出自定义异常
     */
    private static Object fieldValueToObject(String value, String dataType) {
        // 检查输入值是否为null，如果是，则直接返回null
        if (value == null) {
            return null;
        }
        // 检查数据类型是否为原始类型，如果是，则直接返回字符串值
        if (primitiveTypeNames.contains(dataType) || S.equals(dataType, "String")) {
            switch (dataType) {
                case "Integer":
                    return Integer.parseInt(value);
                case "Long":
                    return Long.parseLong(value);
                case "Double":
                    return Double.parseDouble(value);
                case "Boolean":
                    return Boolean.parseBoolean(value);
                case "Short":
                    return Short.parseShort(value);
                case "Byte":
                    return Byte.parseByte(value);
                case "String":
                    return value;
            }
            return value;
        }
        try {
            // 尝试通过JSON解析将字符串值转换为指定数据类型的对象
            return JSON.parseObject(value, Class.forName(dataType));
        } catch (ClassNotFoundException e) {
            // 当指定的数据类型类不存在时，记录错误并抛出自定义异常
            log.error("自定义值类型转换异常", e);
            throw new CustomFieldException("自定义值类型转换异常" + dataType);
        }
    }

    /**
     * 获取实体类字段的值
     *
     * @param entity
     * @param fieldName
     * @return
     * @author 陈广煜
     * @date 2024-09-03
     */
    public static Object getFieldValue(Object entity, String fieldName) {
        if (entity == null || S.isBlank(fieldName)) {
            return null;
        }
        if (fieldName.contains(".")) {
            String end = fieldName.substring(fieldName.indexOf(".") + 1);
            String first = fieldName.substring(0, fieldName.indexOf("."));
            return getFieldValue(ReflectUtil.getFieldValue(entity, first), end);
        } else {
            if (entity instanceof Map) {
                return ((Map) entity).get(fieldName);
            }
            return ReflectUtil.getFieldValue(entity, fieldName);
        }
    }
}
